package visualization;

import java.awt.Color;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import log.Log;

import org.gephi.data.attributes.api.AttributeColumn;
import org.gephi.data.attributes.api.AttributeController;
import org.gephi.data.attributes.api.AttributeModel;
import org.gephi.data.attributes.api.AttributeType;
import org.gephi.graph.api.DirectedGraph;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.GraphController;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.api.MixedGraph;
import org.gephi.graph.api.Node;
import org.gephi.graph.api.UndirectedGraph;
import org.gephi.io.exporter.api.ExportController;
import org.gephi.io.importer.api.Container;
import org.gephi.io.importer.api.ContainerFactory;
import org.gephi.io.importer.api.ImportController;
import org.gephi.io.processor.plugin.DefaultProcessor;
import org.gephi.layout.plugin.AutoLayout;
import org.gephi.layout.plugin.force.StepDisplacement;
import org.gephi.layout.plugin.force.yifanHu.YifanHuLayout;
import org.gephi.layout.plugin.forceAtlas.ForceAtlasLayout;
import org.gephi.partition.api.EdgePartition;
import org.gephi.partition.api.NodePartition;
import org.gephi.partition.api.Part;
import org.gephi.partition.api.PartitionController;
import org.gephi.partition.api.PartitionModel;
import org.gephi.partition.plugin.EdgeColorTransformer;
import org.gephi.partition.plugin.NodeColorTransformer;
import org.gephi.project.api.ProjectController;
import org.gephi.project.api.Workspace;
import org.openide.util.Lookup;

import database.DatabaseGetters;
import form.SelectedItems;

import shared.ColorsForGraph;
import shared.Friend;
import shared.Pair;
import shared.PersonList;

public class CreateGraph  // Klasa SINGLETONowa - tylko 1 obiekt może istnieć
{	
	private static CreateGraph INSTANCE; 

	public static CreateGraph get()
	{
		if (INSTANCE == null)
		{
			INSTANCE = new CreateGraph();
		}
		return INSTANCE;
	}
	
	 ProjectController pc;	
	 Workspace workspace;
	 GraphModel graphModel;
	 Container container;
	 AttributeController ac;
	 AttributeModel attributeModel;	 
	 ImportController importController;
	 
	 ArrayList <Node> nodes = new ArrayList<Node>();
	 ArrayList <Edge> edges = new ArrayList<Edge>();
	 ArrayList <Edge> families = new ArrayList<Edge>();
	 
	 UndirectedGraph undirectedGraph;
	 DirectedGraph directedGraph;
	 MixedGraph mixedGraph;
	 
	 AttributeColumn genderColumn;
	 AttributeColumn relationColumn;
	private CreateGraph()
	{
		 pc = Lookup.getDefault().lookup(ProjectController.class);
		 pc.newProject();		
		 workspace = pc.getCurrentWorkspace();
		 graphModel = Lookup.getDefault().lookup(GraphController.class).getModel();
		 container = Lookup.getDefault().lookup(ContainerFactory.class).newContainer();
		 importController = Lookup.getDefault().lookup(ImportController.class);
		 ac = Lookup.getDefault().lookup(AttributeController.class);
		 attributeModel = ac.getModel();
		 attributeModel.getNodeTable().addColumn("gender", AttributeType.STRING);
		 attributeModel.getEdgeTable().addColumn("relation", AttributeType.STRING);
		 genderColumn = attributeModel.getNodeTable().getColumn("gender");
		 relationColumn = attributeModel.getEdgeTable().getColumn("relation");
		 Log.console("Graph has been generated", Log.priority.Message);

	}
	
	public void setData(PersonList<Friend> friends, SelectedItems selectedItems)
	{
		
		nodes.clear();
		edges.clear();
		families.clear();
		
		// adding nodes
		for( Friend f : friends)
		{
			nodes.add( graphModel.factory().newNode(String.valueOf(f.getId())));
			nodes.get(nodes.size()-1).getNodeData().setLabel(f.getName()+" "+f.getSurname());
			nodes.get(nodes.size()-1).getNodeData().getAttributes().setValue("gender", f.getGender());
		}
		// adding edges
		int nodeId = 0;		
		for( Friend f : friends)
		{
			// all friends to list in n-th friend
			DatabaseGetters.getFriendConnections(f, selectedItems); 
			// for each friend on list
			
			for(String friendId : friends.get(f.getId()).getFriends())
			{
				// finding node for n-th friend
				for(Node n : nodes)
				{
					if(n.getNodeData().getId().equals(friendId))
					{
						edges.add(graphModel.factory().newEdge(nodes.get(nodeId), n, 1.0f, true));
						edges.get(edges.size()-1).getEdgeData().getAttributes().setValue("relation", "14");
						break;
					}						
				}				
			}
			
			DatabaseGetters.getFamilyConnections(f, selectedItems); 
			// for each friend on list
			
			for(Pair<String, String> familymember: friends.get(f.getId()).getFamilyMembers())
			{
				// finding node for n-th friend
				for(Node n : nodes)
				{
					if(n.getNodeData().getId().equals(familymember.getKey()))
					{
						families.add(graphModel.factory().newEdge(n, nodes.get(nodeId), 1.0f, true));
						families.get(families.size()-1).getEdgeData().getAttributes().setValue("relation", familymember.getValue());
						break;
					}						
				}				
			}
			
			nodeId++;
		}
		
		// hold for example
		//Node n0 = graphModel.factory().newNode("n0");
	    //n0.getNodeData().setLabel("Node 0");
        //Edge e1 = graphModel.factory().newEdge(n1, n2);
       
        undirectedGraph = graphModel.getUndirectedGraph();
        directedGraph = graphModel.getDirectedGraph();
        mixedGraph = graphModel.getMixedGraph();
        

        /*for(Node n : nodes)
        	undirectedGraph.addNode(n);
        for(Edge e : edges)
        	undirectedGraph.addEdge(e);
        		
        for(Edge f : families)
        	directedGraph.addEdge(f);*/
        
        for(Node n : nodes)
        	mixedGraph.addNode(n);
        for(Edge e : edges)
        	mixedGraph.addEdge(e);
        		
        for(Edge f : families)
        	mixedGraph.addEdge(f);
       	
        System.out.println("# directed: " + mixedGraph.getDirectedEdges().toArray().length);
        System.out.println("# undirected: " + mixedGraph.getUndirectedEdges().toArray().length);
        System.out.println("# Edgecount: "+ mixedGraph.getEdgeCount());
        
        ColorsForGraph colorsForGraph = new ColorsForGraph();
        ///Partition with gender
    	PartitionController partitionController = Lookup.getDefault().lookup(PartitionController.class);
    	NodePartition genderpart = (NodePartition) partitionController.buildPartition(genderColumn, mixedGraph);
    	System.out.println("# Gender partitions found: "  + genderpart.getPartsCount());
    	NodeColorTransformer nodeColorTransformer = new NodeColorTransformer();
    	for(Part<Node> part : genderpart.getParts())
    	{
    		nodeColorTransformer.getMap().put(part.getValue(), colorsForGraph.getColorsForNodes().get(part.getValue()).getColor());
    	}
    	partitionController.transform(genderpart, nodeColorTransformer);
    	
    	//Partition with relation
    	EdgePartition relationpart = (EdgePartition) partitionController.buildPartition(relationColumn, mixedGraph);
    	System.out.println("# Family relation partitions found: "  + relationpart.getPartsCount());
    	EdgeColorTransformer edgeColorTransformer = new EdgeColorTransformer();
    	for(Part<Edge> part : relationpart.getParts())
    	{
    		edgeColorTransformer.getMap().put(part.getValue(), colorsForGraph.getColorsForEdges().get(part.getValue()).getColor());
    	}
    	partitionController.transform(relationpart, edgeColorTransformer);
    	Log.console("Data has been set to graph", Log.priority.Message);
	}		
	
	public void generateGraphFile() 
	 {   		
		 try
		 {
		    generateGraph();
		    Log.console("File of graph has been generated", Log.priority.Message);
		 }
		 catch(Exception e)
		 {
			 try 
			 {
				java.lang.Thread.sleep(250);
				generateGraph();
				 Log.console("File of graph has been generated", Log.priority.Message);
			 } 
			 catch (InterruptedException e1) 
			 {				
				e1.printStackTrace();
				 Log.console(e1.toString(), Log.priority.Error);
			 }
		 }
	 }	

	 public void generateGraph() 
	 {   
		    importController.process(container, new DefaultProcessor(), workspace);
	        

	        //Layout for 1 minute
	        AutoLayout autoLayout = new AutoLayout(7, TimeUnit.SECONDS);
	        autoLayout.setGraphModel(graphModel);
	        YifanHuLayout firstLayout = new YifanHuLayout(null, new StepDisplacement(10f));
	        ForceAtlasLayout secondLayout = new ForceAtlasLayout(null);
	        AutoLayout.DynamicProperty adjustBySizeProperty = AutoLayout.createDynamicProperty("forceAtlas.adjustSizes.name", Boolean.TRUE, 0.1f);//True after 10% of layout time
	        AutoLayout.DynamicProperty repulsionProperty = AutoLayout.createDynamicProperty("forceAtlas.repulsionStrength.name", new Double(5000.), 0.9f);//500 for the complete period
	        autoLayout.addLayout(firstLayout, 0.8f);
	        autoLayout.addLayout(secondLayout, 0.2f, new AutoLayout.DynamicProperty[]{adjustBySizeProperty, repulsionProperty});
	        //See if graph is well imported
	        //GraphModel graphModel = Lookup.getDefault().lookup(GraphController.class).getModel();
	        //DirectedGraph graph = graphModel.getDirectedGraph();
	        //Log.console("Nodes: " + graph.getNodeCount());
	        //Log.console("Edges: " + graph.getEdgeCount());	        
	        autoLayout.execute();
	        //Export
	        ExportController ec = Lookup.getDefault().lookup(ExportController.class);
	        try {
	            ec.exportFile(new File("graph.gexf"));
	        } catch (IOException ex) {
	            ex.printStackTrace();
	            return;
	        }
	    }
	 
	 public void clearGraph()
	 {
		 if(mixedGraph != null)
		 {
			 mixedGraph.clear();
			 graphModel.clear();		
			 container.closeLoader();
			 pc.closeCurrentWorkspace();
			 pc.cleanWorkspace(workspace);
			 //pc.closeCurrentProject();
		 }
	 }
	 
	 public void reCreateGraph()
	 {
		 pc = Lookup.getDefault().lookup(ProjectController.class);
		 pc.newProject();		
		 workspace = pc.getCurrentWorkspace();
		 graphModel = Lookup.getDefault().lookup(GraphController.class).getModel();
		 container = Lookup.getDefault().lookup(ContainerFactory.class).newContainer();
		 importController = Lookup.getDefault().lookup(ImportController.class);
		 ac = Lookup.getDefault().lookup(AttributeController.class);
		 attributeModel = ac.getModel();
		 attributeModel.getNodeTable().addColumn("gender", AttributeType.STRING);
		 attributeModel.getEdgeTable().addColumn("relation", AttributeType.STRING);
		 genderColumn = attributeModel.getNodeTable().getColumn("gender");
		 relationColumn = attributeModel.getEdgeTable().getColumn("relation");
		 
	 }
	 
	 public ArrayList<Node> getNodes()
	 {
		 return this.nodes;
	 }
	 
	 public ArrayList<Edge> getEdges()
	 {
		 return this.edges;
	 }
}
